package com.cyy.common.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cyy.common.constant.ArticleReadVerifyType;
import com.cyy.common.constant.CacheKey;
import com.cyy.common.constant.Constants;
import com.cyy.common.exception.ProcessException;
import com.cyy.common.mapper.*;
import com.cyy.common.pojo.dto.ArchiveDto;
import com.cyy.common.pojo.query.system.SystemArticleVo;
import com.cyy.common.pojo.dto.ArticleSearchDto;
import com.cyy.common.pojo.entity.*;
import com.cyy.common.pojo.query.system.SystemArticleQuery;
import com.cyy.common.pojo.req.ArticlePushReq;
import com.cyy.common.pojo.req.ArticleSaveOrUpdateReq;
import com.cyy.common.pojo.req.PageArticleReq;
import com.cyy.common.pojo.req.RandomImgReq;
import com.cyy.common.pojo.resp.ArticleDetailsResp;
import com.cyy.common.pojo.vo.api.ApiArchiveVo;
import com.cyy.common.pojo.vo.api.ApiArticleVo;
import com.cyy.common.service.ArticleService;
import com.cyy.common.utils.IpUtils;
import com.cyy.common.utils.PageUtils;
import com.cyy.common.utils.RedisUtils;
import com.cyy.common.utils.RequestContextUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 博客文章表 服务实现类
 * </p>
 *
 * @author 曹云友
 * @since 2024-04-01
 */
@Service
@RequiredArgsConstructor
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {

    private final ArticleTagMapper articleTagMapper;
    private final ArticleLikeMapper articleLikeMapper;
    private final UserMapper userMapper;
    private final CategoryMapper categoryMapper;
    private final RedisUtils redisUtils;
    private final FollowMapper followMapper;


    @Override
    public IPage<ApiArticleVo> queryPage(PageArticleReq req) {
        return baseMapper.queryPage(new Page<>(req.getPageNum(),req.getPageSize()),
                req.getType(),
                req.getBusinessId(),
                Integer.valueOf(Optional.ofNullable((String) StpUtil.getLoginIdDefaultNull()).orElse("-1")));
    }

    @Override
    public ArticleDetailsResp queryDetailsById(Long id) {

        Article article = Optional.ofNullable(baseMapper.selectById(id))
                .orElseThrow(() -> new ProcessException("未找到对应文章信息！"));

        ArticleDetailsResp resp = new ArticleDetailsResp();
        BeanUtils.copyProperties(article, resp);

        // 查询分类
        if (Objects.nonNull(article.getCategoryId())) {
            Category category = categoryMapper.selectOne(Wrappers.<Category>lambdaQuery()
                    .select(Category::getName)
                    .eq(Category::getId, article.getCategoryId())
                    .eq(Category::getStatus, Boolean.TRUE)
            );
            if (Objects.nonNull(category)) {
                resp.setCategoryName(category.getName());
            }
        }


        // 查询tag
        List<ArticleTag> articleTags = articleTagMapper.selectList(Wrappers.<ArticleTag>lambdaQuery()
                .select(ArticleTag::getTagId)
                .eq(ArticleTag::getArticleId, id));
        if (CollectionUtil.isNotEmpty(articleTags)) {
            resp.setTags(articleTags.stream().map(ArticleTag::getTagId).toList());
        }

        // 点赞数
        resp.setLikeCount(Math.toIntExact(articleLikeMapper.selectCount(Wrappers.<ArticleLike>lambdaQuery().eq(ArticleLike::getArticleId, id))));

        // 当前用户是否点赞
        if (StpUtil.isLogin()) {
            resp.setIsLike(articleLikeMapper.exists(Wrappers.<ArticleLike>lambdaQuery()
                    .eq(ArticleLike::getArticleId, id)
                    .eq(ArticleLike::getUserId, StpUtil.getLoginId())
            ));
            // 是否关注 如果是当前用户显示关注
            resp.setIsFollowed(followMapper.exists(Wrappers.<Follow>lambdaQuery()
                    .eq(Follow::getUserId, StpUtil.getLoginId())
                    .eq(Follow::getFollowedUserId, article.getUserId())
            ) || StpUtil.getLoginIdAsInt() == article.getUserId());
        }
        // 是否通过阅读验证
        resp.setIsReadVerifyPass(readVerifyQuery(article.getId()));


        // 用户基础信息查询
        resp.setUserBasic(userMapper.queryUserBasicById(article.getUserId()));

        // 观看计数处理
        boolean isViewInCre = articleViewHandle(article.getId());
        // 设置观看数量
        resp.setViewCount(isViewInCre ? resp.getViewCount() + 1 : resp.getViewCount());

        return resp;
    }

    /**
     * @param id
     */
    private boolean articleViewHandle(Integer id) {
        HttpServletRequest request = RequestContextUtils.getRequest();
        if (Objects.nonNull(request)) {
            String ipAddr = IpUtils.getIpAddr(request);
            String cacheKey = String.join(Constants.COLON, CacheKey.ARTICLE_VIEW, String.valueOf(id));
            Object hget = redisUtils.hget(cacheKey, ipAddr);
            if (Objects.isNull(hget)) {// 如果之前没看过
                // 两小时失效
                redisUtils.hset(cacheKey, ipAddr, "0", 60 * 60 * 2);
                // 对应文章浏览量+1
                baseMapper.update(Wrappers.<Article>lambdaUpdate()
                        .setSql("view_count = view_count +1")
                        .eq(Article::getId, id));
                return true;
            }
        }
        return false;
    }

    @Override
    public IPage<ApiArchiveVo> pageArchive() {
        Page<?> page = PageUtils.getPage();
        // 分页步骤
        // 1.先按照时间查出分页的具体月份和数量以及ID列表
        // 2.通过ID列表组合起来进行查询
        // 3.查询完毕之后归集到结果里面
        IPage<ApiArchiveVo> res = baseMapper.pageArchiveVos(page);
        List<ApiArchiveVo> records = res.getRecords();
        if (CollectionUtil.isNotEmpty(records)) {
            Set<String> articleIds = new HashSet<>();
            records.forEach(item -> articleIds.addAll(Arrays.asList(item.getArticleIds().split(","))));
            // 查询详细信息
            List<ArchiveDto> dtos = baseMapper.selectArchiveDtosByIds(articleIds);
            // 先归集到map中，防止重复创建map
            Map<Integer, List<ArchiveDto>> dtomap = dtos.stream().collect(Collectors.groupingBy(ArchiveDto::getId));
            // 归集
            records.forEach(i -> {
                this.archiveIdMatch(i, dtomap);
            });
        }
        return res;
    }


    private void archiveIdMatch(ApiArchiveVo vo, Map<Integer, List<ArchiveDto>> dtomap) {
        List<String> ids = Arrays.asList(vo.getArticleIds().split(","));

        ArrayList<ArchiveDto> archives = new ArrayList<>();

        ids.forEach(id -> {
            archives.addAll(dtomap.get(Integer.valueOf(id)));
        });

        vo.setArchives(archives);

    }


    @Override
    public List<ArticleSearchDto> searchArticleByKeyWords(String keywords) {
        List<Article> articles = baseMapper.selectList(Wrappers.<Article>lambdaQuery()
                .select(Article::getId, Article::getTitle, Article::getSummary)
                .eq(Article::getIsPush, Boolean.TRUE)
                .and(wrp -> {
                    wrp.like(Article::getSummary, keywords)
                            .or()
                            .like(Article::getTitle, keywords);
                })
                .orderByDesc(Article::getIsTop, Article::getCreateTime));

        return articles.stream().map(item -> {
            ArticleSearchDto archiveVo = new ArticleSearchDto();
            archiveVo.setId(item.getId());
            archiveVo.setTitle(item.getTitle().replaceAll(keywords, Constants.HIGH_LIGHT_TAG_PREFIX + keywords + Constants.HIGH_LIGHT_TAG_SUFFIX));
            archiveVo.setSummary(item.getSummary().replaceAll(keywords, Constants.HIGH_LIGHT_TAG_PREFIX + keywords + Constants.HIGH_LIGHT_TAG_SUFFIX));
            return archiveVo;
        }).toList();
    }

    @Override
    public Integer like(Integer id) {
        // 先查询是否存在对应的记录
        ArticleLike articleLike = articleLikeMapper.selectOne(Wrappers.<ArticleLike>lambdaQuery()
                .eq(ArticleLike::getArticleId, StpUtil.getLoginIdAsInt())
                .last(Constants.LIMIT_1));
        if (Objects.isNull(articleLike)) {
            ArticleLike like = new ArticleLike();
            like.setArticleId(id);
            like.setUserId(StpUtil.getLoginIdAsInt());
            articleLikeMapper.insert(like);
            return 1;
        } else {
            articleLikeMapper.deleteById(articleLike);
            return 0;
        }
    }

    @Override
    public IPage<SystemArticleVo> getPage(SystemArticleQuery query) {
        return baseMapper.getPage(PageUtils.getPage(), query, StpUtil.hasRole(Constants.ADMIN) ? null : StpUtil.getLoginIdAsInt());
    }

    @Override
    public Integer saveOrUpdateArticle(ArticleSaveOrUpdateReq req) {

        if (Objects.isNull(req.getId())) {
            Article article = new Article();
            article.setUserId(StpUtil.getLoginIdAsInt());
            saveOrUpdateParamSet(req, article);
            this.save(article);
            // 设置tag
            articleTagSet(article.getId(), req.getTags());
            return article.getId();
        } else {
            Article byId = getById(req.getId());
            if (Objects.isNull(byId)) {
                throw new ProcessException("未找到对应文章信息");
            }
            saveOrUpdateParamSet(req, byId);
            // 设置tag
            articleTagSet(byId.getId(), req.getTags());
            this.updateById(byId);
            return req.getId();
        }
    }

    /**
     * 文章标签设置
     *
     * @param id   文章ID
     * @param tags 标签列表
     */
    private void articleTagSet(Integer id, Integer[] tags) {
        //策略：先删后插
        // 删除
        articleTagMapper.delete(Wrappers.<ArticleTag>lambdaQuery()
                .eq(ArticleTag::getArticleId, id));
        // 插入
        if (ArrayUtils.isNotEmpty(tags)) {
            articleTagMapper.saveArticleTags(id, tags);
        }

    }

    @Override
    public com.cyy.common.pojo.vo.system.SystemArticleVo queryEditVoById(Integer id) {

        Article byId = Optional.ofNullable(getById(id))
                .orElseThrow(() -> new ProcessException("未找到执行博客信息"));
        com.cyy.common.pojo.vo.system.SystemArticleVo res = new com.cyy.common.pojo.vo.system.SystemArticleVo();
        BeanUtils.copyProperties(byId, res);
        List<ArticleTag> articleTags = articleTagMapper.selectList(Wrappers.<ArticleTag>lambdaQuery()
                .select(ArticleTag::getTagId)
                .eq(ArticleTag::getArticleId, id));
        if (CollectionUtil.isNotEmpty(articleTags)) {
            res.setTags(articleTags.stream().map(ArticleTag::getTagId).collect(Collectors.toList()));
        }
        return res;
    }

    @Override
    public String randomImg(RandomImgReq req) {


        // 博天API
        String type = StringUtils.isBlank(req.getImgType()) ? "suiji" : req.getImgType();
        String api = "https://api.btstu.cn/sjbz/api.php?lx=" + type + "&format=json";

        String s = HttpUtil.get(api);
        return JSON.parseObject(s).getString("imgurl");
    }


    /**
     * 文章发布
     *
     * @param req
     */
    @Override
    public void push(ArticlePushReq req) {

        // 先查询是否存在
        Article byId = Optional.ofNullable(getById(req.getId()))
                .orElseThrow(() -> new ProcessException("未找到对应文章信息"));
        byId.setCategoryId(req.getCategoryId());
        byId.setTitle(req.getTitle());
        byId.setSummary(req.getSummary());
        byId.setCover(StringUtils.isBlank(req.getCover()) ? null : req.getCover());
        byId.setIsPush(1); // 发布
        byId.setIsDraft(0); // 不再是草稿
        byId.setContent(req.getContent());
        byId.setIsTop(req.getIsTop());
        byId.setIsOriginal(req.getIsOriginal());
        byId.setReproductionUrl(StringUtils.isBlank(req.getReproductionUrl()) ? null : req.getReproductionUrl());
        // 标签设置
        articleTagSet(req.getId(), req.getTags());
        updateById(byId);
    }

    @Override
    public Boolean readVerifyQuery(Integer id) {
        // 先查询是否存在对应的文章
        Article article = baseMapper.selectOne(Wrappers.<Article>lambdaQuery()
                .select(Article::getReadVerifyType)
                .eq(Article::getId, id)
        );
        if (Objects.isNull(article)) {
            throw new ProcessException("参数错误！");
        }

        ArticleReadVerifyType readVerifyType = ArticleReadVerifyType.fromInt(article.getReadVerifyType());
        // 分类型校验
        if (Objects.nonNull(readVerifyType)) {
            return switch (readVerifyType) {
                case OFF, FOLLOW -> true;// 目前 FOLLOW 暂时没有实现逻辑，暂时先放行
                case LIKE -> { // 如果是点赞验证
                    if (!StpUtil.isLogin()) {
                        yield false;
                    }
                    yield articleLikeMapper.exists(Wrappers.<ArticleLike>lambdaQuery()
                            .eq(ArticleLike::getUserId, StpUtil.getLoginId())
                            .eq(ArticleLike::getArticleId, id));
                }
            };
        }

        // 其余都返回false
        return false;
    }

    /**
     * 参数设置
     *
     * @param req
     * @param article
     */
    private void saveOrUpdateParamSet(ArticleSaveOrUpdateReq req, Article article) {
        if (StringUtils.isAnyEmpty(req.getContent(), req.getTitle())) {
            throw new ProcessException("必要参数 text title 不能为空");
        }
        article.setCategoryId(req.getCategoryId());
        article.setTitle(req.getTitle());
        article.setSummary(StringUtils.isBlank(req.getSummary()) ? null : req.getSummary());
        article.setCover(StringUtils.isBlank(req.getCover()) ? null : req.getCover());
        article.setContent(req.getContent());
        article.setIsTop(req.getIsTop());
        article.setIsOriginal(req.getIsOriginal());
        article.setReproductionUrl(StringUtils.isBlank(req.getReproductionUrl()) ? null : req.getReproductionUrl());
    }


}
